#include "stdafx.h"
#include "NameValuePair.h"
#include "surveyAlligatorException.h"
#include "CSVGenerator.h"

namespace SurveyAlligator {

	using namespace System;
	using namespace System::ComponentModel;
	using namespace System::Collections;
	using namespace System::Collections::Generic;
	using namespace System::Windows::Forms;
	using namespace System::Data;
	

	/// Parameter: _fileName is the fully qualified file name
	CSVGenerator::CSVGenerator(System::String^ _fileName, SurveyAlligatorConfig^ _config) 
	{
		try {
				fileName = _fileName;
			    debugmsg="";
				DEBUG_INFO = false;
				
				
				int pos = _fileName->LastIndexOf("\\");
				System::IO::DirectoryInfo^ dir = gcnew System::IO::DirectoryInfo(_fileName);
				if(!dir->Exists)
				{
					if(pos > 0)
					{
						array<System::String^>^ elements = _fileName->Split('\\');
						fileName = elements[elements->Length-1];
						directoryName = _fileName->Substring(0,pos-1);
					}
					else throw gcnew surveyAlligatorException("File path Invalid - Full path to file required: "+System::Environment::NewLine+unSlashFileName(_fileName));
				
					if(fileName->Trim()->Length == 0)
					throw gcnew surveyAlligatorException("File path Invalid - File name missing: "+System::Environment::NewLine+unSlashFileName(_fileName));

					if(directoryName->Trim()->Length == 0)
					throw gcnew surveyAlligatorException("File path Invalid - Directory name(s) missing: "+System::Environment::NewLine+unSlashFileName(_fileName));

				
				}
				else throw gcnew surveyAlligatorException("File path Invalid - File name missing: "+System::Environment::NewLine+unSlashFileName(_fileName));
				
				dir = (gcnew System::IO::DirectoryInfo(directoryName));
				
				if(!dir->Exists)
				throw gcnew surveyAlligatorException("One or more directory do not exist in path: "+System::Environment::NewLine+unSlashFileName(dir->FullName));


				fileName = directoryName+"\\\\"+fileName;
				System::IO::FileInfo^ file = (gcnew System::IO::FileInfo(fileName)); 
				
				if(file->Exists)
				fileExists = true;
				else fileExists = false;
				
				globalConfig = _config;

		}
		catch(System::ArgumentException^ e)
		{
			throw (gcnew surveyAlligatorException("CSVGenrator object creation exception."+System::Environment::NewLine+"CSV filename may not be valid."+System::Environment::NewLine+e->Message));
		}
		catch(surveyAlligatorException^ e)
		{
			throw (gcnew surveyAlligatorException("CSVGenrator object creation exception."+System::Environment::NewLine+e->getMessage())); 
		}
	}

		


	System::String^ CSVGenerator::getDebugMsg()
	{
		return this->debugmsg;			 
	}



	void CSVGenerator::clearDebugMsg()
	{
		this->debugmsg = "";			 
	}

	
	
	void CSVGenerator::startDebug()
	{
		this->DEBUG_INFO = true;
	}
	

	void CSVGenerator::endDebug()
	{
		this->DEBUG_INFO = false;
	}


	
	void CSVGenerator::printDebug(System::String^ s)
	{
	   if (DEBUG_INFO)
	   {
		   debugmsg += s + Environment::NewLine;
	   }
			
	}



	bool CSVGenerator::createFile()
	{
	   return true;
	}
			
			/// <summary>
			/// Parameters: 
			///        formElement: A pointer to a html form element whose inputs we want to extract
			///                     The inputs will be extracted in the same order they appear in a HTML DOM structure of 
			///                     the form element.
			/// Returns: If the given html element is a input tag, then it depends how it returns its value.
			///          If the given element is a
			//                  text, return the value.
			///                 checkbox, collects values of all the check boxes with the same names in the inputElementList and returns them as
			///           
			/// <summary>
			///
	
    /*
	 * Parameter: A pointer to a html form (<form>) element whose input data needs to be extracted. 
	 *
	 */
	System::Collections::ArrayList^ CSVGenerator::extractHtmlInputTagValues(System::Windows::Forms::HtmlElement^ formElement)
	{
	printDebug("Entering extractHtmlInputTagValues()");
    printDebug("     Start Extraction ...");
			   
	System::Collections::ArrayList^ extracted = gcnew System::Collections::ArrayList();
				

	// A dictionary to keep track of all radio and checkbox tags visited so far
	// We need this because checkboxes and radio types are extracted based on names
	// not their id. Hence, there can be multiple of these with the same name, we want to 
	// remember if we already extracted them or not.
	System::Collections::Generic::IDictionary<System::String^, System::String^>^ visited;
	visited = gcnew System::Collections::Generic::Dictionary<System::String^, System::String^>();

	// All input elements extracted so far. Elements in this list are stored as NameValuePair Objects
	System::Collections::ArrayList^ allInpElem = gcnew System::Collections::ArrayList();
	// Extract all input elements
	System::Windows::Forms::HtmlElementCollection^ all = formElement->All;
	System::Collections::IEnumerator^ ienuma = all->GetEnumerator();

	printDebug( "     Initializing local data done ...");
	HtmlElement^ elem; System::String^ elemTagName;
				/*

	Scan through all sub element tags in the given form tag
	and extract values if they are of type select or input or textarea
			*/
	while((ienuma != nullptr) && (ienuma->MoveNext()))
	{
		elem = (HtmlElement^)ienuma->Current;
		elemTagName = elem->TagName;
		elemTagName = elemTagName->ToUpper();

		if((elemTagName->Equals("INPUT")) || (elemTagName->Equals("TEXTAREA")) || (elemTagName->Equals("SELECT")))
		{
			// We are guaranteed that id attribute of the element exists.
			allInpElem->Add(elem);				
			
		} // end of if type is input or textarea or select
			
	} // end of while
   

	printDebug("     Scaning all input tags done ...");
			
	/* Now we have all the input elements that should be extracted in the same order
	   as they appear in the form.
	   Lets process them.
	*/
	elem = nullptr; elemTagName = nullptr;
	String^ elemType;
	NameValuePair^ data;

	/*
	  ----------------
	  DO NOT REMOVE: Psuedocode explaing how the input values are extracted
	  ----------------
	  
	  For each element tag whose value is to be extracted, 
	  Use different ways to extract based on their type. The high level psuedocode of this
	  for loop is as follows:
	for(all elements to be extracted)
	{
	    if(element tag is input)
		{
		   if(element type is text)
		   {
		     1. Create a name value pair object
			 2. Add the elements id as name and value as value
			 3. Add this object to the extracted list
			}
			   if(element type is radio)
			   {
			     1. See if this element was already visited
				 2. If yes, then skip it
			    else, extract all the input tags with the same name  and this type as this one.
					:
					   a. Extract the selected radio: See if this tag has a value attribute, if it has extract the value
				      else extract the inner text of the tag as the value
					   b. Add it to the extracted list
					   }
			   if(element type is checkbox)
			   {
		        Extract it appropriately in the same way as for radio, except that
			    extract all those items that were selected.
				
				   }
		}
		else if (element tag is select )
		{
			Use recursion to extract groups of selected values.

		}
		else if (element tag is )
		{
           extract similar to text											
						
		}
	}
				*/

				for(int i=0; i<allInpElem->Count;i++)
				{
				    data = nullptr; elemType = nullptr;
					elem = (HtmlElement^)allInpElem[i];
					elemTagName = elem->TagName;
					elemTagName = elemTagName->ToUpper();
                    
					/*
					   
					   If element is an input tag, then its type can be either 
					   text or radio or checkbox.
					*/
					if(elemTagName->Equals("INPUT"))
					{
						printDebug("     Reading input tag with id: " + elem->Id + " ...");
						elemType = elem->GetAttribute("type");
						if(elemType == nullptr)
							throw (gcnew surveyAlligatorException("CSVGenerator: \nInput tag with id: " + elem->Id + " missing <type> attribute."));
						
						
						elemType = elemType->ToUpper();
						printDebug("     Element with " + elem->Id + " is of type: " + elemType +" ...");
						

						if(elemType->Equals("TEXT"))
						{
						    data = gcnew NameValuePair();
							data->setName(elem->Id);
							data->setValue(elem->GetAttribute("value"));
							extracted->Add(data);
							printDebug("     Extracted values for element with " + elem->Id + " and of type: " + elemType +" ...");
							
						}
						else if(elemType->Equals("RADIO"))
						{
							data = gcnew NameValuePair();
							String^ name = elem->Name; String^ value = "";
							if((name == nullptr) || (name->Trim()->Equals("")))
								throw gcnew surveyAlligatorException("Radio input tags must have <name> attribute. \nInput tag with id: "+ elem->Id + " missing this attribute.");
							    


							if(!(visited->ContainsKey(elemType+"##"+name)))
							{
							    // If this the first time we are seeing this radio tag with this name
								// extract all radio with the same tag name and find the selected value
								data->setName(name);
								HtmlElementCollection^ rsCollection = formElement->GetElementsByTagName("input");
								System::Collections::IEnumerator^ rscenum = rsCollection->GetEnumerator();
								HtmlElement^ rrElem; String^ tmp = ""; 
								while((rscenum != nullptr) && (rscenum->MoveNext()))
								{
									rrElem = (HtmlElement^) rscenum->Current;
									if((rrElem->GetAttribute("type")->ToUpper()->Equals("RADIO")) && (rrElem->Name->Equals(name)))
									{
									    // check if this radio was selected

										if(rrElem->GetAttribute("checked")->ToLower()->Equals( "true"))
										{
											// It is possible that the value attribute contains the
											// value to extract. If it does, extract it.
											tmp = rrElem->GetAttribute("value");
											
											// If value attribute was not provided, just extract the inner html text as the value
											if(tmp == nullptr)
											tmp = rrElem->InnerText;
											if(tmp == nullptr)
											tmp = "";											
											value = tmp;
											break;
										}
									} // end of if radio tag with the same name as the first one we visited
									

								} // end of while

								// Collect the value of the selected radio
								data->setValue(value);
								printDebug("     Extracted values for element with name " + data->getName() + " and of type: " + data->getValue() +" ...");
								// We will remember that we have extracted data from this radio group
								visited->Add(elemType+"##"+name, elem->Id);
								extracted->Add(data);
							} // end of if element not visited
							
						} // end of if radio
						else if(elemType->Equals("CHECKBOX"))
						{
						   // If input is of type checkbox; do the same stuff as for radio
							 
							data = gcnew NameValuePair();
							String^ name = elem->Name; String^ value = "";
							if((name == nullptr) || (name->Trim()->Equals("")))
								throw gcnew surveyAlligatorException("Checkbox input tags must have <name> attribute. \nInput tag with id: "+ elem->Id + " missing this attribute.");
							    

							if(!(visited->ContainsKey(elemType+"##"+name)))
							{
							    // If this the first time we are seeing this radio/checkbox tag with this name
								// extract all radio with the same tag name and find the selected value
								data->setName(name);
								HtmlElementCollection^ rsCollection = formElement->GetElementsByTagName("input");
								System::Collections::IEnumerator^ rscenum = rsCollection->GetEnumerator();
								HtmlElement^ rrElem; String^ tmp = ""; 
								while((rscenum != nullptr) && (rscenum->MoveNext()))
								{
									rrElem = (HtmlElement^) rscenum->Current;
									if((rrElem->GetAttribute("type")->ToUpper()->Equals("CHECKBOX")) && (rrElem->Name->Equals(name)))
									{
									    // check if this radio was selected

										if(rrElem->GetAttribute("checked")->ToLower()->Equals( "true"))
										{
											
											tmp = rrElem->GetAttribute("value");
											
											if(tmp == nullptr || tmp->Length==0)
											tmp = rrElem->InnerText;
											if(tmp == nullptr)
											tmp = "";		
											if(tmp->Length > 0)
											value += tmp+"\n";
											
										
										}
									} // end of if checkbox tag with the same name as the first one we visited
									

								} // end of while
								value = value->TrimEnd('\n');
								data->setValue(value);

								// We will remember that we have extracted data from this radio group
								visited->Add(elemType+"##"+name, elem->Id);
								extracted->Add(data);
						
						
							} // end of if already visited contains this element 


						}// end of if input is of type checkbox



					} // end of if tag is input
					else if(elemTagName->Equals("SELECT"))
					{
					  /// Extract all the optgroup
						System::Collections::ArrayList^ optList = gcnew System::Collections::ArrayList();
						if(elem != nullptr)
						{
						   data = gcnew NameValuePair();
						   String^ name = elem->Name; String^ selectValue = "";
													
						
						  if((name == nullptr) || (name->Trim()->Equals("")))
								throw gcnew Exception("Select input tags must have <name> attribute. \nInput tag with id: "+ elem->Id + " missing this attribute.");
							
							
							optList = this->getAllOptions(elem);
						
						
							String^ tmpVal; HtmlElement^ tmpElem;
						   for(int iopt = 0; iopt<optList->Count; iopt++)
						   {
							   tmpElem = (HtmlElement^) optList[iopt];

							   if(tmpElem->GetAttribute("selected")->ToLower()->Equals( "true"))
							   {
										
							   tmpVal = "";
							   tmpVal = tmpElem->GetAttribute("value");
								
							   if(tmpVal == nullptr || tmpVal->Length==0)
							   tmpVal = tmpElem->InnerText;
							   
							   if(tmpVal != nullptr && tmpVal != "")
							   tmpVal += "\n";

							   selectValue += tmpVal;
							   } //  end of if checked

		
						   } // end of for
						   selectValue = selectValue->TrimEnd('\n'); 	
						   data->setName(name);
						   data->setValue(selectValue);
						   extracted->Add(data);

						}

                    } // end of select
					else if (elemTagName->Equals("TEXTAREA"))
					{
							String^ valtmp;
					         data = gcnew NameValuePair();
							 data->setName(elem->Id);
							 valtmp = elem->GetAttribute("value");
							 
							 valtmp = valtmp->Replace("\r\n", " ");
							 //valtmp = valtmp->Replace("\r", " ");
							 valtmp = valtmp->Replace("\t", " ");
							
							 data->setValue(valtmp);
							 extracted->Add(data);
							 printDebug("     Input tag " + elem->Id + " is Text Area ...");
							
					
					 } // end of if textarea

				
				 }// end of for



			   return extracted;

			} // end of extractHtmlInputTagValues

            
			
			
			System::Collections::ArrayList^ CSVGenerator::getAllOptions(HtmlElement^ element)
			{

				System::Collections::ArrayList^ optionlist = gcnew System::Collections::ArrayList();
				System::Windows::Forms::HtmlElementCollection^ opL = element->Children;
				System::Collections::IEnumerator^ ienumOpL = opL->GetEnumerator();
				HtmlElement^ elem;
				while((ienumOpL != nullptr) && (ienumOpL->MoveNext()))
				{
					elem = (HtmlElement^) ienumOpL->Current;
					if(elem->TagName->ToUpper()->Equals("OPTION"))
					{
						optionlist->Add(elem);				  
					}
					else if(elem->TagName->ToUpper()->Equals("OPTGROUP")) 
					{
						optionlist->AddRange(getAllOptions(elem));
					}

				} // end of while

			return optionlist;

			
			} // end of getAllOptions



			void CSVGenerator::appendToFile(System::Windows::Forms::HtmlElement^ formElement)
			{
			   			   
				if(formElement == nullptr)
				throw gcnew surveyAlligatorException("CsvGenerator.appendToFile: No <form> element found. Input tags must be enclosed in form tags.");
				printDebug("     Directory name: " + directoryName);				
				printDebug("     File name: " + fileName);				
				printDebug("     Starting append to file ...");
			   System::Data::DataTable^ dataTable = gcnew System::Data::DataTable();
			   System::Data::DataColumn^ dataColumn;
			   System::Data::DataRow^ dataRow = dataRow = dataTable->NewRow();
			   int i = 0;

			   printDebug("      Extracting input tag values ...");
			   System::Collections::ArrayList^ dataValues = this->extractHtmlInputTagValues(formElement);
			   NameValuePair^ element;
			   
			   for(int i=0; i<dataValues->Count; i++)
			   {
				   
				      dataColumn = gcnew System::Data::DataColumn();
				      dataColumn->DataType=Type::GetType("System.String");
				      element = (NameValuePair^) dataValues[i];
					  dataColumn->ColumnName = element->getName();
					  dataTable->Columns->Add(dataColumn);				   
					  dataRow[i] = element->getValue();
					   
				
				      				      
				} // end of while
				
			   printDebug("      Building csv row data ...");
			   
			   dataTable->Rows->Add(dataRow);

			   CSVParser::CSVWriter csvwriter;
			   System::IO::StreamWriter^ writer;
			   if(this->fileExists)
			   {
			      /// File exists, append the html tags values as csv row data without appending the 
			      /// column headers
				  writer = System::IO::File::AppendText(fileName);
				  csvwriter.WriteToStream(writer, dataTable, false, true); 
				  printDebug("     Appending csv data to existing file ...");
			   
			   }
			   else
			   {
			      /// File does not exist, create it and add the csv data.
			      /// Add the html tag names as the column headers and values as csv row data
				   writer = System::IO::File::CreateText(fileName);
			 	   csvwriter.WriteToStream(writer, dataTable, true, true); 
			       printDebug("     Creating new csv file and appending data ...");
			   
			   }
							
			   writer->Close();
				
			   printDebug ("	Done appending data ... ");		  
			} // end of appendToFile



			/*
				Summary:
				   Parameter:
				      formElement: A HtmlElement that is a representation of the <form> tag
					  primElement: A HtmlElement that is a representation of a <input> tag of text type
					  which has the value that will used to find a rwo in the csv file and replace.
				  Algorithm:
				      If the csv file exists, 
							a. Find the row whose any column has the value same as the 
							   value of the primElement.
							b. If value found, remove the entire row from file
							c. Append new value to the end of file
					  else append value to the end of file.
				  Output:
				     a. If the file exists, 
							1.If row matched , then remove it from file and replace it with provided value
							2.If rows in file did not match, just append new data to end of file.
					 b. else, just append to end of file

			*/

			void CSVGenerator::replaceInFile(System::Windows::Forms::HtmlElement^ formElement, System::Windows::Forms::HtmlElement^ primaryHtmlTag)
			{
				// First read in all data from the existing csv file
				printDebug("Entering replaceInFile ...");
				
				if(primaryHtmlTag == nullptr)
				throw gcnew surveyAlligatorException("CSVGenerator.replaceInFile: No input tag provided with id that should be used to replace in csv file.");
				
				if(formElement == nullptr)
				throw gcnew surveyAlligatorException("CSVGenerator.replaceInFile: No <form> element found. Input tags must be enclosed in form tags.");
				


				if(this->fileExists)
				{
					printDebug("     Csv file exists ... ");
					
					CSVParser::CSVReader reader;
					
					printDebug("     Extracting data from csv file ...");
					System::IO::StreamReader^ freader = System::IO::File::OpenText(this->fileName); 
					System::Data::DataTable^ dataTable = gcnew System::Data::DataTable();
				    dataTable = reader.Parse(freader, false);
					freader->Close();
					printDebug("     Done extracting data ...");
					
					System::Windows::Forms::HtmlElementCollection^ elements = formElement->All;
					System::Collections::IEnumerator^ ienumE = elements->GetEnumerator();

					int ic=0; System::String^ defaultColNamePrefix = gcnew System::String("NoColumnName");
					//System::String^ tmp;
					
					printDebug("     Scanning data rows ...");
					// Since we dont know which column contains the value of the primaryHtmlTag
					// And since we dont know if the file has column headers, we assign temporary
					// column header names so that we can search  in each column 
					int colCnt = 0;
					System::Data::DataColumnCollection^ cols = dataTable->Columns;
					System::Collections::IEnumerator^ ienumC = cols->GetEnumerator();
					System::Data::DataColumn^ tmpCol;
					while(ienumC != nullptr && ienumC->MoveNext())
					{
						tmpCol = (System::Data::DataColumn^)ienumC->Current;
						tmpCol->ColumnName = defaultColNamePrefix+" " +colCnt;
						colCnt++;
						
					}
					

					// Now search each column starting from left to right
					// As soon as we find a column that contains the value of primaryHtmlTag,
					/// Remove that row so we can replace with the current form data
					array<System::Data::DataRow^>^ matchedRows;
					for(int i=0; i<colCnt; i++)
					{
						matchedRows = dataTable->Select("`"+defaultColNamePrefix+" "+i+"`='"+(primaryHtmlTag->GetAttribute("value"))+"'");
						if(matchedRows->Length > 0)
						{
							dataTable->Rows->Remove(matchedRows[0]);
							printDebug("     Found row that has to be replaced. Removing row data ...");
							break;
						}
					}


			   // Add the existing form data back into the csv file first
			   // At this point, we have removed the line in the csv that contains the 
			   // value that was matched by the value of the primaryHtmlTag, if it existed
			   // in the csv file

			   printDebug("     Writing all data back to csv file");
			   System::IO::StreamWriter^ fwriter  = System::IO::File::CreateText(this->fileName);
    		   CSVParser::CSVWriter writer;
			   writer.WriteToStream(fwriter, dataTable, false, true);
			   fwriter->Close();
			   	
			   	
			   printDebug("     Appending new data to csv file ... ");	
			   // Now write the data that replaces the old line in the csv
			   this->appendToFile(formElement);
			    

				} // end of if file exists
			   else this->appendToFile(formElement);
			   printDebug("     Replacing data done ...");
               
			
		} // end of replaceInFile
		

	String^ CSVGenerator::unSlashFileName(String^ _fn)
	{
		
		if(_fn->Trim()->Length != 0)
			return _fn->Replace("\\\\", "\\");
		else return "";
	
	}



} // end of namespace
